#include "ClientEngine.hpp"

ClientEngine::ClientEngine(sf::RenderWindow* renderWindow, NetworkHandler* network, std::string mapName,
        Camera* gameCamera, byte clientIndex, Packet_GameState* gameState){
    //Copy pointers
	window = renderWindow;
    networkHandler = network;
    camera = gameCamera;

    map = 0;
    MapLoader::loadMap(mapName, &map, &regionManager);
    
	//Initialize entity information.
    playerIndex = clientIndex;
    playerCount = 0;
    entityManager.map = map;
	entityManager.maxPlayers = gameState->maxClients;
    entityManager.players = new Player*[entityManager.maxPlayers];
    memset(entityManager.players, 0, sizeof(Player*) * entityManager.maxPlayers);

	//Setup debug counters
	fpsCounter.setInterval(1000);
	mopsCounter.setInterval(1000);
	mipsCounter.setInterval(1000);

	//Setup UI
	fpsLabel.setPosition(5, 5);
    fpsLabel.setString(12, sf::Color::White, sf::Text::Regular, "");
	ui.addElement(&fpsLabel);

	mipsLabel.setPosition(5, 19);
    mipsLabel.setString(12, sf::Color::White, sf::Text::Regular, "");
	ui.addElement(&mipsLabel);

	mopsLabel.setPosition(5, 33);
    mopsLabel.setString(12, sf::Color::White, sf::Text::Regular, "");
	ui.addElement(&mopsLabel);

    ui.addElement(&chatBox);
    ui.onResize(camera->getWidth(), camera->getHeight());

    run = true;
	//Start game timer
    gameTimer.restart();
}

/** 
* Handle input events from SFML.
*/
void ClientEngine::handleEvents(){
    // Process events
    sf::Event event;
    while(window->pollEvent(event)){
        // Close window : exit
        if(event.type == sf::Event::Closed)
            window->close();
        else if(event.type == sf::Event::LostFocus){
            hasFocus = false;
            entityManager.player->setMove(0);
        }else if(event.type == sf::Event::GainedFocus)
            hasFocus = true;
        else if(event.type == sf::Event::Resized){
            camera->setWidth(window->getSize().x);
            camera->setHeight(window->getSize().y);
            window->setView(sf::View(sf::FloatRect(0, 0, camera->getWidth(), camera->getHeight())));
            ui.onResize(camera->getWidth(), camera->getHeight());
        }else if(event.type == sf::Event::TextEntered && hasFocus){
            if(ui.hasKeyboard){
                ui.keyEvent(event.text.unicode);
            }
        }else if(event.type == sf::Event::MouseWheelMoved && hasFocus){
            ui.wheelEvent(event.mouseWheel.delta);
        }
    }
}


/** 
* Handle mouse and keyboard input.
*/
void ClientEngine::handleInput(){
    if(hasFocus) ui.update(frameTime, sf::Mouse::getPosition(*window).x, sf::Mouse::getPosition(*window).y,
            sf::Mouse::isButtonPressed(sf::Mouse::Left), sf::Mouse::isButtonPressed(sf::Mouse::Right));

	//Handle keyboard events if UI doesn't have keyboard control and window has focus.
    if(!ui.hasKeyboard && hasFocus){
        unsigned char currentDir = entityManager.player->direction;
        unsigned char inputDir = 0;
        if(sf::Keyboard::isKeyPressed(sf::Keyboard::W))
            inputDir = inputDir | Entity::dir_N;
        if(sf::Keyboard::isKeyPressed(sf::Keyboard::S))
            inputDir = inputDir | Entity::dir_S;
        if(sf::Keyboard::isKeyPressed(sf::Keyboard::A))
            inputDir = inputDir | Entity::dir_W;
        if(sf::Keyboard::isKeyPressed(sf::Keyboard::D))
            inputDir = inputDir | Entity::dir_E;
        entityManager.player->setMove(inputDir);

        if(sf::Keyboard::isKeyPressed(sf::Keyboard::Return)){
            ui.giveKeyboard(chatBox.getTextBox());
            entityManager.player->setMove(0);
            keepAliveTime = frameTime;
        }

		if(inputDir != currentDir){
			networkHandler->addMessage(Message::m_c_PlayerMove, &entityManager.player->direction);
            keepAliveTime = frameTime;
        }
    }
    //UI Text input is handled in events. 
}

/** 
* Handle network messages.
* @param messages Message queue containing received messages.
* @param serverTime The expected time on the server.
*/
void ClientEngine::handleMessages(MessageQueue* messages, unsigned int serverTime){
    cout << messages->size << " message(s) received." << endl;

	mipsCounter.increase(serverTime, messages->size);

	//Iterate all messages
	for(MessageIterator message = messages->begin(); *message; ++message){

		//Get expected message latency, discard if unrealistic.
        unsigned int latency = serverTime - (*message)->serverTime;
		if(latency > 10000){
			continue;
		}

		switch((*message)->code){
			case Message::m_b_ChatMessage:{
				Packet_ChatMessage* msg = (Packet_ChatMessage*) (*message)->buffer;

				if(msg->client == 255){
					chatBox.pushMessage((char*)msg->message);
				}else{
					chatBox.pushMessage(std::string(entityManager.players[msg->client]->name).append(": ").append((char*) msg->message));
				}
				break;
			}case Message::m_s_PlayerMove: {
			
				Packet_ServerEntityLocation* el = (Packet_ServerEntityLocation*) (*message)->buffer;
				cout << latency << "ms. Move message: " << (int)el->direction << endl;
            
				if(entityManager.players[el->client] != 0){
					if(serverTime < entityManager.players[el->client]->lastNetworkUpdateTime){
						cout << "Discarding old move command" << endl;
						continue;
					}
                
					entityManager.players[el->client]->lastNetworkUpdateTime = serverTime;
                
					if((entityManager.players[el->client]->direction & Entity::dir_Horizontal) == 0 &&
						(el->direction & Entity::dir_Horizontal) != 0){
							entityManager.players[el->client]->x = el->x;
                        
					}else if((entityManager.players[el->client]->direction & Entity::dir_Vertical) == 0 &&
						(el->direction & Entity::dir_Vertical) != 0){
							entityManager.players[el->client]->y = el->y;
					}
				
					if(el->client == playerIndex){

						entityManager.players[el->client]->x = el->x;
						entityManager.players[el->client]->y = el->y;
						entityManager.players[el->client]->setMove(el->direction);
						float dist = distance(sf::Vector2f(el->x, el->y), entityManager.player);
						if(dist < 0.7 || dist > 20){
							cout << latency << "ms. Clipping player" << endl;
							entityManager.player->x = el->x;
							entityManager.player->y = el->y;
						}
						entityManager.player->setDeltas(entityManager.player->x - el->x, entityManager.player->y - el->y);
					}else{
						entityManager.players[el->client]->setMove(el->direction);

						float dist = distance(sf::Vector2f(el->x, el->y), entityManager.players[el->client]);
						if(dist < 1.1 || dist > 16){
							entityManager.players[el->client]->x = el->x;
							entityManager.players[el->client]->y = el->y;
						}
					}
					entityManager.players[el->client]->setDeltas(entityManager.players[el->client]->x - el->x, entityManager.players[el->client]->y - el->y);
				}
				break;
			} case Message::m_s_EntityMove:{
				Packet_ServerEntityLocation* el = (Packet_ServerEntityLocation*) (*message)->buffer;
				if(!entityManager.entities[el->client])
					continue;
            
				if((entityManager.entities[el->client]->direction & Entity::dir_Horizontal) == 0 &&
						(el->direction & Entity::dir_Horizontal) != 0){
					entityManager.entities[el->client]->x = el->x;

				}else if((entityManager.entities[el->client]->direction & Entity::dir_Vertical) == 0 &&
						(el->direction & Entity::dir_Vertical) != 0){
					entityManager.entities[el->client]->y = el->y;
				}
				entityManager.entities[el->client]->setMove(el->direction);

				float dist = distance(sf::Vector2f(el->x, el->y), entityManager.entities[el->client]);
				if(dist < 1.1 || dist > 20){
					entityManager.entities[el->client]->x = el->x;
					entityManager.entities[el->client]->y = el->y;
				}
				((ServerEntity*)entityManager.entities[el->client])->setDeltas(entityManager.entities[el->client]->x - el->x, entityManager.entities[el->client]->y - el->y);
				break;
			}case Message::m_s_NewClient: {

				Packet_ClientState* el = (Packet_ClientState*) (*message)->buffer;
				cout << "New Client " << el->name << " joined" << endl;
				entityManager.players[el->client] = new Player(map, el->name, GameDrawer::Instance()->spriteManager.getSpriteSet("c1"), el->x, el->y, el->direction);
				cout << el->x << endl;
				break;
			}case Message::m_s_Rename: {

				Packet_ClientRename* cr = (Packet_ClientRename*) (*message)->buffer;
				entityManager.players[cr->client]->name = cr->name;
				break;
			}case Message::m_b_ClientDisconnect: {

				byte index = *(byte*)(*message)->buffer;
				delete entityManager.players[index];
				entityManager.players[index] = 0;
                
                //Check if the server has disconnected this client.
                if(index == playerIndex){
                    run = false;
                }

				break;
			}case Message::m_s_NewEntity: {

				Packet_EntityState* es = (Packet_EntityState*) (*message)->buffer;
				Character* character = (Character*)entityManager.createEntity(es->script, es->index);
                character->x = es->x;
                character->y = es->y;
                character->direction = es->direction;
				break;
			}case Message::m_s_DestroyEntity: {

				entityManager.destroyEntity(*((uint16_t*)((*message)->buffer)));
				break;
			}
		}
	}
}

/** 
* Updates game state.
*/
void ClientEngine::update(){
    
	//Get the time of this game frame.
    frameTime = gameTimer.getElapsedTime().asMilliseconds();

    handleEvents();
    handleInput();

	//Update entity states.
	entityManager.update(frameTime);

    //Update region states
    regionManager.update(frameTime);
    
	//Send keepalive move packet if one hasnt been sent in over a second.
    if(frameTime - keepAliveTime  > 1000){
        keepAliveTime = frameTime;
		networkHandler->addMessage(Message::m_c_PlayerMove, &entityManager.player->direction);
    }

	//Send any messages that are in queue.
	int messageCount = networkHandler->outQueue->size;
    networkHandler->sendMessages();
	if(networkHandler->outQueue->size != messageCount)
		mopsCounter.increase(frameTime, messageCount);
}

/**
 * Handles client disconnecting from a server.
 * Notify the server of disconnect.
 */
void ClientEngine::disconnect(){
    //Clear current message queue and just send dc message.
    networkHandler->outQueue->clearQueue();

    byte reasonCode = 0;
    networkHandler->addMessage(Message::m_b_ClientDisconnect, &reasonCode);
    networkHandler->sendMessages();
    networkHandler->closeConnection();
}

/** 
* Draws the game.
*/
void ClientEngine::draw(sf::RenderTarget& target, sf::RenderStates states) const{
	//Draw map background
	GameDrawer::Instance()->drawMapBackground(map, camera);

	entityManager.player->setPosition(entityManager.player->x - GameDrawer::Instance()->pixelOffsetX, entityManager.player->y - GameDrawer::Instance()->pixelOffsetY);
    target.draw(*entityManager.player, states);
    for(byte i = 0; i < entityManager.maxPlayers; i++){
        if(entityManager.players[i] != 0){
            entityManager.players[i]->setPosition(entityManager.players[i]->x - GameDrawer::Instance()->pixelOffsetX, entityManager.players[i]->y - GameDrawer::Instance()->pixelOffsetY);
            target.draw(*entityManager.players[i], states);
        }
    }

	//Draw entities
	for(int i = 0; i < entityManager.size; i++){
		if(entityManager.entities[i]){
			Character* character = (Character*)entityManager.entities[i];
			sf::RenderStates entityPosition = character->getTransform();
			entityPosition.transform.translate(character->x - (float)GameDrawer::Instance()->pixelOffsetX,
				character->y - (float)GameDrawer::Instance()->pixelOffsetY);
			target.draw(*character, entityPosition);
		}
	}

	//Draw map foreground
	GameDrawer::Instance()->drawMapForeground(map, camera);

	//Draw the UI
    target.draw(ui, states);
}

ClientEngine::~ClientEngine(){
    window = 0;
    camera->setTarget(0);
    camera = 0;
    networkHandler = 0;

    ui.clear();

    delete map;
    map = 0;
}
